import json
import os
import csv
from datetime import datetime
from .logger import get_logger

# ANSI 颜色代码
COLORS = {
    'HEADER': '\033[95m',
    'BLUE': '\033[94m',
    'GREEN': '\033[92m',
    'YELLOW': '\033[93m',
    'RED': '\033[91m',
    'ENDC': '\033[0m',
    'BOLD': '\033[1m',
    'UNDERLINE': '\033[4m'
}

logger = get_logger()

class Reporter:
    """报告生成器类，用于生成各种格式的扫描报告"""
    
    VALID_FORMATS = {'all', 'txt', 'json', 'html', 'csv'}
    
    def __init__(self, output_format='all'):
        """初始化报告生成器
        
        Args:
            output_format: 报告输出格式，可选值：'all'、'txt'、'json'、'html'、'csv'
            
        Raises:
            ValueError: 当output_format不是有效的格式时
        """
        if output_format not in self.VALID_FORMATS:
            raise ValueError(f"无效的输出格式: {output_format}. 有效格式为: {', '.join(self.VALID_FORMATS)}")
            
        self.output_format = output_format
        self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        try:
            # 创建基础结果目录
            self.result_dir = 'result'
            os.makedirs(self.result_dir, exist_ok=True)
            
            # 创建本次扫描的时间戳目录
            self.scan_dir = os.path.join(self.result_dir, self.timestamp)
            os.makedirs(self.scan_dir, exist_ok=True)
            
            logger.info(f"扫描结果将保存到目录: {self.scan_dir}")
        except OSError as e:
            logger.error(f"创建目录时发生错误: {str(e)}")
            raise
    
    def _validate_scan_data(self, results, scan_info):
        """验证扫描数据的有效性
        
        Args:
            results: 扫描结果列表
            scan_info: 扫描信息字典
            
        Returns:
            bool: 数据是否有效
            
        Raises:
            ValueError: 当数据格式无效时
        """
        if not isinstance(results, list):
            raise ValueError("扫描结果必须是列表类型")
            
        if not isinstance(scan_info, dict):
            raise ValueError("扫描信息必须是字典类型")
            
        required_scan_info = {'start_time', 'end_time', 'total_hosts', 'total_ports'}
        missing_fields = required_scan_info - set(scan_info.keys())
        if missing_fields:
            raise ValueError(f"扫描信息缺少必要字段: {', '.join(missing_fields)}")
            
        for result in results:
            required_fields = {'target', 'port', 'status', 'service', 'latency', 'service_info'}
            if not all(field in result for field in required_fields):
                raise ValueError(f"扫描结果缺少必要字段: {required_fields - set(result.keys())}")
        
        return True
    
    def _safe_str(self, value):
        """安全地将值转换为字符串，处理None和特殊字符
        
        Args:
            value: 要转换的值
            
        Returns:
            str: 转换后的字符串
        """
        if value is None:
            return ''
        return str(value).replace('<', '&lt;').replace('>', '&gt;')
    
    def _format_timestamp(self, timestamp):
        """格式化时间戳
        
        Args:
            timestamp: Unix时间戳
            
        Returns:
            str: 格式化的时间字符串
        """
        try:
            return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
        except (TypeError, ValueError):
            logger.warning(f"无效的时间戳: {timestamp}")
            return 'N/A'
    
    def generate_report(self, results, scan_info):
        """生成指定格式的报告
        
        Args:
            results: 扫描结果列表
            scan_info: 扫描信息字典
            
        Raises:
            ValueError: 当数据格式无效时
        """
        try:
            if not results:
                logger.warning("没有扫描结果可供报告")
                return
                
            # 验证输入数据
            self._validate_scan_data(results, scan_info)
            
            # 根据指定格式生成报告
            format_handlers = {
                'txt': self._generate_txt_report,
                'json': self._generate_json_report,
                'html': self._generate_html_report,
                'csv': self._generate_csv_report
            }
            
            if self.output_format == 'all':
                for handler in format_handlers.values():
                    try:
                        handler(results, scan_info)
                    except Exception as e:
                        logger.error(f"生成{handler.__name__}报告时发生错误: {str(e)}")
            else:
                handler = format_handlers.get(self.output_format)
                if handler:
                    handler(results, scan_info)
                    
        except Exception as e:
            logger.error(f"生成报告时发生错误: {str(e)}")
            raise
    
    def _write_file(self, filename, content, mode='w', encoding='utf-8'):
        """安全地写入文件
        
        Args:
            filename: 文件名
            content: 要写入的内容
            mode: 写入模式
            encoding: 文件编码
            
        Raises:
            OSError: 当写入文件失败时
        """
        try:
            with open(filename, mode, encoding=encoding) as f:
                f.write(content)
        except OSError as e:
            logger.error(f"写入文件 {filename} 时发生错误: {str(e)}")
            raise
    
    def _get_service_info(self, info, service_type):
        """获取特定服务类型的信息
        
        Args:
            info: 服务信息字典
            service_type: 服务类型
            
        Returns:
            list: 服务详细信息列表
        """
        details = []
        
        try:
            if info['name'] == service_type:
                if service_type == 'ftp':
                    if info.get('os_type'):
                        details.append(f"操作系统: {self._safe_str(info['os_type'])}")
                    if info.get('anonymous') is not None:
                        details.append(f"匿名访问: {'允许' if info['anonymous'] else '禁止'}")
                    if info.get('features'):
                        details.append(f"支持特性: {', '.join(map(self._safe_str, info['features']))}")
                
                elif service_type == 'ssh':
                    ssh_fields = {
                        'protocol_version': 'SSH协议版本',
                        'key_type': '密钥类型',
                        'key_bits': '密钥长度',
                        'fingerprint': '密钥指纹',
                        'auth_methods': '认证方式',
                        'encryption_algorithms': '加密算法'
                    }
                    
                    for field, label in ssh_fields.items():
                        if info.get(field):
                            value = info[field]
                            if isinstance(value, list):
                                details.append(f"{label}: {', '.join(map(self._safe_str, value))}")
                            else:
                                details.append(f"{label}: {self._safe_str(value)}")
                
                elif service_type in ['http', 'https']:
                    http_fields = {
                        'http_code': 'HTTP状态码',
                        'http_title': '网页标题',
                        'http_server': 'Web服务器'
                    }
                    
                    for field, label in http_fields.items():
                        if info.get(field):
                            details.append(f"{label}: {self._safe_str(info[field])}")
                    
                    if info.get('http_headers'):
                        details.append("HTTP头信息:")
                        for key, value in info['http_headers'].items():
                            details.append(f"  - {self._safe_str(key)}: {self._safe_str(value)}")
                    
                    if info.get('ssl_info'):
                        ssl_info = info['ssl_info']
                        details.append("SSL/TLS信息:")
                        ssl_fields = {
                            'version': '版本',
                            'cipher': '加密套件',
                            'cert_subject': '证书主体',
                            'cert_issuer': '证书颁发者',
                            'cert_expire': '证书过期时间'
                        }
                        
                        for field, label in ssl_fields.items():
                            if ssl_info.get(field):
                                details.append(f"  {label}: {self._safe_str(ssl_info[field])}")
        
        except Exception as e:
            logger.error(f"处理{service_type}服务信息时发生错误: {str(e)}")
            details.append(f"处理服务信息时发生错误: {str(e)}")
        
        return details
    
    def _get_status_style(self, status):
        """获取状态对应的样式类
        
        Args:
            status: 端口状态
            
        Returns:
            tuple: (状态文本, 状态样式类)
        """
        status_map = {
            'open': ('开放', 'status open'),
            'closed': ('关闭', 'status closed'),
            'filtered': ('被过滤', 'status filtered')
        }
        return status_map.get(status, (status, 'status'))
    
    def _colored(self, text, color):
        """为文本添加颜色
        
        Args:
            text: 要着色的文本
            color: 颜色代码
        """
        return f"{COLORS.get(color, '')}{text}{COLORS['ENDC']}"
    
    def _generate_txt_report(self, results, scan_info):
        """Generate plain text report"""
        filename = os.path.join(self.scan_dir, 'scan_report.txt')
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                # 标题使用紫色加粗
                f.write(self._colored("端口扫描报告\n", 'HEADER'))
                f.write(self._colored("=" * 50 + "\n", 'HEADER'))
                
                # 扫描信息使用蓝色
                f.write(self._colored("扫描开始时间: ", 'BLUE') + f"{datetime.fromtimestamp(scan_info['start_time'])}\n")
                f.write(self._colored("扫描结束时间: ", 'BLUE') + f"{datetime.fromtimestamp(scan_info['end_time'])}\n")
                f.write(self._colored("扫描主机数: ", 'BLUE') + f"{scan_info['total_hosts']}\n")
                f.write(self._colored("扫描端口数: ", 'BLUE') + f"{scan_info['total_ports']}\n\n")
                
                current_target = None
                for result in sorted(results, key=lambda x: (x['target'], x['port'])):
                    if current_target != result['target']:
                        current_target = result['target']
                        f.write(f"\n{self._colored('目标主机: ', 'BOLD')}{self._colored(current_target, 'UNDERLINE')}\n")
                        f.write(self._colored("-" * 30 + "\n", 'BLUE'))
                    
                    status_map = {
                        'open': ('开放', 'GREEN'),
                        'closed': ('关闭', 'RED'),
                        'filtered': ('被过滤', 'YELLOW')
                    }
                    status_text, status_color = status_map.get(result['status'], (result['status'], 'BLUE'))
                    service = result['service'] if result['service'] else "未知"
                    latency = f"{result['latency']}ms" if result['latency'] else "N/A"
                    
                    # 端口信息使用不同颜色
                    port_info = f"端口 {result['port']}: {self._colored(status_text, status_color)} "
                    port_info += f"({self._colored(service, 'BLUE')}) - "
                    port_info += f"响应时间: {latency}\n"
                    f.write(port_info)
                    
                    if result['service_info'] and result['status'] == 'open':
                        info = result['service_info']
                        if info['product']:
                            f.write(f"  {self._colored('产品:', 'BLUE')} {info['product']}\n")
                        if info['version']:
                            f.write(f"  {self._colored('版本:', 'BLUE')} {info['version']}\n")
                        if info.get('banner'):
                            f.write(f"  {self._colored('Banner:', 'BLUE')} {info['banner']}\n")
                            
                            # 添加FTP服务的详细信息
                            if info['name'] == 'ftp':
                                if info.get('os_type'):
                                    f.write(f"  {self._colored('操作系统:', 'BLUE')} {info['os_type']}\n")
                                if info.get('anonymous'):
                                    f.write(f"  {self._colored('匿名访问:', 'BLUE')} {'允许' if info['anonymous'] else '禁止'}\n")
                                if info.get('features'):
                                    f.write(f"  {self._colored('支持特性:', 'BLUE')} {', '.join(info['features'])}\n")
                            
                            # 添加SSH服务的详细信息
                            if info['name'] == 'ssh':
                                if info.get('protocol_version'):
                                    f.write(f"  {self._colored('SSH协议版本:', 'BLUE')} {info['protocol_version']}\n")
                                if info.get('key_type'):
                                    f.write(f"  {self._colored('密钥类型:', 'BLUE')} {info['key_type']}\n")
                                if info.get('key_bits'):
                                    f.write(f"  {self._colored('密钥长度:', 'BLUE')} {info['key_bits']}位\n")
                                if info.get('fingerprint'):
                                    f.write(f"  {self._colored('密钥指纹:', 'BLUE')} {info['fingerprint']}\n")
                                if info.get('auth_methods'):
                                    f.write(f"  {self._colored('认证方式:', 'BLUE')} {', '.join(info['auth_methods'])}\n")
                                if info.get('encryption_algorithms'):
                                    f.write(f"  {self._colored('加密算法:', 'BLUE')} {', '.join(info['encryption_algorithms'])}\n")
                            
                            # HTTP/HTTPS服务的详细信息
                            if info['name'] in ['http', 'https']:
                                if info.get('http_code'):
                                    f.write(f"  {self._colored('HTTP状态码:', 'BLUE')} {info['http_code']}\n")
                                if info.get('http_title'):
                                    f.write(f"  {self._colored('网页标题:', 'BLUE')} {info['http_title']}\n")
                                if info.get('http_server'):
                                    f.write(f"  {self._colored('Web服务器:', 'BLUE')} {info['http_server']}\n")
                                if info.get('http_headers'):
                                    f.write(f"  {self._colored('HTTP头信息:', 'BLUE')}\n")
                                    for key, value in info['http_headers'].items():
                                        f.write(f"    - {key}: {value}\n")
                                if info.get('ssl_info'):
                                    ssl_info = info['ssl_info']
                                    f.write(f"  {self._colored('SSL/TLS信息:', 'BLUE')}\n")
                                    if ssl_info.get('version'):
                                        f.write(f"    - 版本: {ssl_info['version']}\n")
                                    if ssl_info.get('cipher'):
                                        f.write(f"    - 加密套件: {ssl_info['cipher']}\n")
                                    if ssl_info.get('cert_subject'):
                                        f.write(f"    - 证书主体: {ssl_info['cert_subject']}\n")
                                    if ssl_info.get('cert_issuer'):
                                        f.write(f"    - 证书颁发者: {ssl_info['cert_issuer']}\n")
                                    if ssl_info.get('cert_expire'):
                                        f.write(f"    - 证书过期时间: {ssl_info['cert_expire']}\n")
                            
                            # 数据库服务的详细信息
                            if info['name'] in ['mysql', 'postgresql', 'mssql', 'oracle', 'mongodb']:
                                if info.get('db_version'):
                                    f.write(f"  {self._colored('数据库版本:', 'BLUE')} {info['db_version']}\n")
                                if info.get('db_type'):
                                    f.write(f"  {self._colored('数据库类型:', 'BLUE')} {info['db_type']}\n")
                                if info.get('auth_method'):
                                    f.write(f"  {self._colored('认证方式:', 'BLUE')} {info['auth_method']}\n")
                                if info.get('requires_ssl'):
                                    f.write(f"  {self._colored('SSL要求:', 'BLUE')} {'是' if info['requires_ssl'] else '否'}\n")
                            
                            # 邮件服务的详细信息
                            if info['name'] in ['smtp', 'pop3', 'imap']:
                                if info.get('mail_version'):
                                    f.write(f"  {self._colored('邮件服务版本:', 'BLUE')} {info['mail_version']}\n")
                                if info.get('auth_methods'):
                                    f.write(f"  {self._colored('支持的认证方式:', 'BLUE')} {', '.join(info['auth_methods'])}\n")
                                if info.get('starttls_supported'):
                                    f.write(f"  {self._colored('STARTTLS支持:', 'BLUE')} {'是' if info['starttls_supported'] else '否'}\n")
                                if info.get('ssl_tls_supported'):
                                    f.write(f"  {self._colored('SSL/TLS支持:', 'BLUE')} {'是' if info['ssl_tls_supported'] else '否'}\n")
                            
                            # 远程桌面服务的详细信息
                            if info['name'] in ['rdp', 'vnc']:
                                if info.get('security_level'):
                                    f.write(f"  {self._colored('安全级别:', 'BLUE')} {info['security_level']}\n")
                                if info.get('encryption_level'):
                                    f.write(f"  {self._colored('加密级别:', 'BLUE')} {info['encryption_level']}\n")
                                if info.get('auth_methods'):
                                    f.write(f"  {self._colored('认证方式:', 'BLUE')} {', '.join(info['auth_methods'])}\n")
                                if info.get('os_info'):
                                    f.write(f"  {self._colored('操作系统信息:', 'BLUE')} {info['os_info']}\n")
            
            logger.info(f"文本报告已生成: {filename}")
        except Exception as e:
            logger.error(f"生成文本报告时发生错误: {str(e)}")
    
    def _generate_json_report(self, results, scan_info):
        """Generate JSON report"""
        filename = os.path.join(self.scan_dir, 'scan_report.json')
        try:
            report = {
                'scan_info': {
                    'start_time': datetime.fromtimestamp(scan_info['start_time']).isoformat(),
                    'end_time': datetime.fromtimestamp(scan_info['end_time']).isoformat(),
                    'total_hosts': scan_info['total_hosts'],
                    'total_ports': scan_info['total_ports']
                },
                'results': results
            }
            
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(report, f, indent=2, ensure_ascii=False)
            
            logger.info(f"JSON报告已生成: {filename}")
        except Exception as e:
            logger.error(f"生成JSON报告时发生错误: {str(e)}")
    
    def _generate_html_report(self, results, scan_info):
        """Generate HTML report"""
        filename = os.path.join(self.scan_dir, 'scan_report.html')
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                f.write("""
<!DOCTYPE html>
<html>
<head>
    <title>端口扫描报告</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        :root {
            --primary-color: #2196F3;
            --success-color: #4CAF50;
            --warning-color: #FFC107;
            --danger-color: #F44336;
            --border-radius: 12px;
            --card-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 20px;
            background-color: #f0f2f5;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 24px;
            border-radius: var(--border-radius);
            box-shadow: var(--card-shadow);
        }
        
        h1 {
            color: var(--primary-color);
            margin-bottom: 24px;
            padding-bottom: 12px;
            border-bottom: 2px solid var(--primary-color);
            font-size: 28px;
        }
        
        .summary {
            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
            padding: 20px;
            border-radius: var(--border-radius);
            margin-bottom: 24px;
            box-shadow: var(--card-shadow);
        }
        
        .summary h2 {
            margin-top: 0;
            color: var(--primary-color);
            font-size: 22px;
            margin-bottom: 16px;
        }
        
        .stats {
            display: flex;
            flex-wrap: wrap;
            gap: 16px;
            margin-bottom: 20px;
        }
        
        .stat-card {
            flex: 1;
            min-width: 180px;
            background: white;
            padding: 16px;
            border-radius: var(--border-radius);
            box-shadow: var(--card-shadow);
            transition: transform 0.2s ease;
        }
        
        .stat-card:hover {
            transform: translateY(-2px);
        }
        
        .stat-card h3 {
            margin: 0;
            color: var(--primary-color);
            font-size: 16px;
            margin-bottom: 8px;
        }
        
        .stat-card p {
            margin: 0;
            font-size: 18px;
            font-weight: 500;
        }
        
        table {
            width: 100%;
            border-collapse: separate;
            border-spacing: 0;
            margin-bottom: 24px;
            border-radius: var(--border-radius);
            overflow: hidden;
            box-shadow: var(--card-shadow);
        }
        
        th, td {
            padding: 16px;
            text-align: left;
            border-bottom: 1px solid #eee;
        }
        
        th {
            background: linear-gradient(135deg, var(--primary-color) 0%, #1976D2 100%);
            color: white;
            font-weight: 500;
        }
        
        tr:last-child td {
            border-bottom: none;
        }
        
        tr:nth-child(even) {
            background-color: #f8f9fa;
        }
        
        tr:hover {
            background-color: #f5f5f5;
        }
        
        .status {
            font-weight: 500;
            padding: 6px 12px;
            border-radius: 20px;
            display: inline-block;
            min-width: 80px;
            text-align: center;
        }
        
        .status.open { 
            background: linear-gradient(135deg, var(--success-color) 0%, #388E3C 100%);
            color: white;
        }
        
        .status.closed { 
            background: linear-gradient(135deg, var(--danger-color) 0%, #D32F2F 100%);
            color: white;
        }
        
        .status.filtered { 
            background: linear-gradient(135deg, var(--warning-color) 0%, #FFA000 100%);
            color: black;
        }
        
        .service-details {
            margin-top: 8px;
            font-size: 14px;
            color: #666;
            line-height: 1.8;
        }
        
        .action-controls {
            display: flex;
            align-items: center;
            gap: 16px;
            margin-bottom: 24px;
            padding: 16px;
            background: white;
            border-radius: var(--border-radius);
            box-shadow: var(--card-shadow);
        }
        
        .action-group {
            display: flex;
            gap: 12px;
            align-items: center;
        }
        
        .action-group:not(:last-child) {
            padding-right: 16px;
            border-right: 2px solid #eee;
        }
        
        .btn {
            border: none;
            padding: 10px 20px;
            border-radius: var(--border-radius);
            cursor: pointer;
            font-weight: 500;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        
        .btn i {
            font-size: 16px;
        }
        
        .copy-btn {
            background: linear-gradient(135deg, var(--primary-color) 0%, #1976D2 100%);
            color: white;
        }
        
        .copy-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(33,150,243,0.3);
        }
        
        .filter-btn {
            background: #f5f5f5;
            color: #333;
        }
        
        .filter-btn:hover {
            background: #e0e0e0;
            transform: translateY(-1px);
        }
        
        .filter-btn.active {
            background: linear-gradient(135deg, var(--primary-color) 0%, #1976D2 100%);
            color: white;
        }
        
        .http-link {
            color: var(--primary-color);
            text-decoration: none;
            padding: 4px 12px;
            border-radius: 4px;
            background: #e3f2fd;
            transition: all 0.3s ease;
            display: inline-block;
            margin-top: 4px;
        }
        
        .http-link:hover {
            background: #bbdefb;
            text-decoration: none;
            transform: translateY(-1px);
        }
        
        @media (max-width: 768px) {
            .stats {
                flex-direction: column;
            }
            
            .stat-card {
                width: 100%;
            }
            
            .action-controls {
                flex-direction: column;
            }
            
            .action-group {
                width: 100%;
                justify-content: center;
                padding: 10px 0;
            }
            
            .action-group:not(:last-child) {
                border-right: none;
                border-bottom: 2px solid #eee;
            }
            
            table {
                display: block;
                overflow-x: auto;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>端口扫描报告</h1>
        
        <div class="summary">
            <h2>扫描摘要</h2>
            <div class="stats">
                <div class="stat-card">
                    <h3>开始时间</h3>
                    <p>""" + datetime.fromtimestamp(scan_info['start_time']).strftime('%Y-%m-%d %H:%M:%S') + """</p>
                </div>
                <div class="stat-card">
                    <h3>结束时间</h3>
                    <p>""" + datetime.fromtimestamp(scan_info['end_time']).strftime('%Y-%m-%d %H:%M:%S') + """</p>
                </div>
                <div class="stat-card">
                    <h3>扫描主机数</h3>
                    <p>""" + str(scan_info['total_hosts']) + """</p>
                </div>
                <div class="stat-card">
                    <h3>扫描端口数</h3>
                    <p>""" + str(scan_info['total_ports']) + """</p>
                </div>
                <div class="stat-card">
                    <h3>开放端口数</h3>
                    <p>""" + str(len([r for r in results if r['status'] == 'open'])) + """</p>
                </div>
            </div>
        </div>

        <!-- 操作按钮组 -->
        <div class="action-controls">
            <div class="action-group">
                <button class="btn copy-btn" onclick="copyToClipboard()">
                    <i class="fas fa-copy"></i>复制报告
                </button>
            </div>
            <div class="action-group">
                <button class="btn filter-btn active" onclick="togglePortFilter('all')">
                    <i class="fas fa-list"></i>显示全部端口
                </button>
                <button class="btn filter-btn" onclick="togglePortFilter('open')">
                    <i class="fas fa-check-circle"></i>仅显示开放端口
                </button>
            </div>
        </div>

        <table>
            <tr>
                <th>序号</th>
                <th>目标主机</th>
                <th>端口</th>
                <th>状态</th>
                <th>服务</th>
                <th>详细信息</th>
                <th>响应时间</th>
            </tr>
""")
                
                status_map = {
                    'open': '开放',
                    'closed': '关闭',
                    'filtered': '被过滤'
                }
                
                for index, result in enumerate(sorted(results, key=lambda x: (x['target'], x['port'])), 1):
                    status_class = result['status'].lower()
                    status_text = status_map.get(result['status'], result['status']).upper()
                    service = result['service'] if result['service'] else "未知"
                    latency = f"{result['latency']}ms" if result['latency'] else "N/A"
                    
                    # Prepare service details
                    details = []
                    if result['service_info'] and result['status'] == 'open':
                        info = result['service_info']
                        if info['product']:
                            details.append(f"产品: {info['product']}")
                        if info['version']:
                            details.append(f"版本: {info['version']}")
                        if info.get('banner'):
                            details.append(f"Banner: {info['banner']}")
                        
                        # 添加FTP服务的详细信息
                        if info['name'] == 'ftp':
                            if info.get('os_type'):
                                details.append(f"操作系统: {info['os_type']}")
                            if info.get('anonymous'):
                                details.append(f"匿名访问: {'允许' if info['anonymous'] else '禁止'}")
                            if info.get('features'):
                                details.append(f"支持特性: {', '.join(info['features'])}")
                        
                        # 添加SSH服务的详细信息
                        if info['name'] == 'ssh':
                            if info.get('protocol_version'):
                                details.append(f"SSH协议版本: {info['protocol_version']}")
                            if info.get('key_type'):
                                details.append(f"密钥类型: {info['key_type']}")
                            if info.get('key_bits'):
                                details.append(f"密钥长度: {info['key_bits']}位")
                            if info.get('fingerprint'):
                                details.append(f"密钥指纹: {info['fingerprint']}")
                            if info.get('auth_methods'):
                                details.append(f"认证方式: {', '.join(info['auth_methods'])}")
                            if info.get('encryption_algorithms'):
                                details.append(f"加密算法: {', '.join(info['encryption_algorithms'])}")
                        
                        # HTTP/HTTPS服务的详细信息
                        if info['name'] in ['http', 'https']:
                            if info.get('http_code'):
                                details.append(f"HTTP状态码: {info['http_code']}")
                            if info.get('http_title'):
                                details.append(f"网页标题: {info['http_title']}")
                            if info.get('http_server'):
                                details.append(f"Web服务器: {info['http_server']}")
                            if info.get('http_headers'):
                                details.append("HTTP头信息:")
                                for key, value in info['http_headers'].items():
                                    details.append(f"  - {key}: {value}")
                            if info.get('ssl_info'):
                                ssl_info = info['ssl_info']
                                details.append("SSL/TLS信息:")
                                if ssl_info.get('version'):
                                    details.append(f"  版本: {ssl_info['version']}")
                                if ssl_info.get('cipher'):
                                    details.append(f"  加密套件: {ssl_info['cipher']}")
                                if ssl_info.get('cert_subject'):
                                    details.append(f"  证书主体: {ssl_info['cert_subject']}")
                                if ssl_info.get('cert_issuer'):
                                    details.append(f"  证书颁发者: {ssl_info['cert_issuer']}")
                                if ssl_info.get('cert_expire'):
                                    details.append(f"  证书过期时间: {ssl_info['cert_expire']}")
                            # 添加访问链接
                            protocol = 'https' if info.get('ssl') or info['name'] == 'https' else 'http'
                            url = f"{protocol}://{result['target']}:{result['port']}"
                            details.append(f'<a href="{url}" target="_blank" class="http-link">访问网站</a>')
                    
                    details_html = '<br>'.join(details) if details else '无详细信息'
                    
                    # 写入表格行
                    f.write(f"""
            <tr class="port-row">
                <td>{index}</td>
                <td>{result['target']}</td>
                <td>{result['port']}</td>
                <td><span class="status {status_class}">{status_text}</span></td>
                <td>{service}</td>
                <td class="service-details">{details_html}</td>
                <td>{latency}</td>
            </tr>""")
                
                f.write("""
            </table>
        </div>

    <script>
    // 添加端口过滤功能
    function togglePortFilter(filter) {
        // 更新按钮状态
        const buttons = document.querySelectorAll('.filter-btn');
        buttons.forEach(btn => {
            btn.classList.remove('active');
            if (btn.textContent.includes(filter === 'all' ? '全部' : '开放')) {
                btn.classList.add('active');
            }
        });
        
        // 获取所有端口行
        const rows = document.querySelectorAll('table tr:not(:first-child)');
        let visibleCount = 0;
        
        rows.forEach(row => {
            const status = row.querySelector('.status').textContent;
            if (filter === 'all') {
                row.style.display = '';
                visibleCount++;
            } else {
                // 检查端口状态是否为"开放"
                if (status === '开放') {
                    row.style.display = '';
                    visibleCount++;
                } else {
                    row.style.display = 'none';
                }
            }
        });
        
        // 更新序号
        updateRowNumbers();
    }
    
    // 更新行号
    function updateRowNumbers() {
        let number = 1;
        const rows = document.querySelectorAll('table tr:not(:first-child)');
        rows.forEach(row => {
            if (row.style.display !== 'none') {
                row.querySelector('td:first-child').textContent = number++;
            }
        });
    }
    
    // 页面加载完成后初始化
    document.addEventListener('DOMContentLoaded', () => {
        updateRowNumbers();
    });
    
    // 复制功能
    function copyToClipboard() {
        const rows = document.querySelectorAll('table tr');
        let text = '端口扫描报告\\n\\n';
        
        // Add summary
        const startTime = document.querySelector('.stats .stat-card:nth-child(1) p').textContent;
        const endTime = document.querySelector('.stats .stat-card:nth-child(2) p').textContent;
        const totalHosts = document.querySelector('.stats .stat-card:nth-child(3) p').textContent;
        const totalPorts = document.querySelector('.stats .stat-card:nth-child(4) p').textContent;
        const openPorts = document.querySelector('.stats .stat-card:nth-child(5) p').textContent;
        
        text += `扫描开始时间: ${startTime}\\n`;
        text += `扫描结束时间: ${endTime}\\n`;
        text += `扫描主机数: ${totalHosts}\\n`;
        text += `扫描端口数: ${totalPorts}\\n`;
        text += `开放端口数: ${openPorts}\\n\\n`;
        
        // Add table contents (only visible rows)
        rows.forEach((row, index) => {
            if (index === 0 || row.style.display === 'none') return; // Skip header row and hidden rows
            const cells = row.querySelectorAll('td');
            const target = cells[1].textContent;
            const port = cells[2].textContent;
            const status = cells[3].textContent;
            const service = cells[4].textContent;
            const details = cells[5].textContent;
            const latency = cells[6].textContent;
            
            text += `${target}:${port} - ${status} (${service})\\n`;
            if (details !== '无详细信息') {
                text += `  详细信息: ${details}\\n`;
            }
            text += `  响应时间: ${latency}\\n`;
        });
        
        navigator.clipboard.writeText(text).then(() => {
            const btn = document.querySelector('.copy-btn');
            btn.innerHTML = '<i class="fas fa-check"></i>已复制';
            setTimeout(() => {
                btn.innerHTML = '<i class="fas fa-copy"></i>复制报告';
            }, 2000);
        });
    }
    </script>
</body>
</html>
""")
            
            logger.info(f"HTML报告已生成: {filename}")
        except Exception as e:
            logger.error(f"生成HTML报告时发生错误: {str(e)}")
    
    def _generate_csv_report(self, results, scan_info):
        """生成CSV格式报告
        
        Args:
            results: 扫描结果列表
            scan_info: 扫描会话信息
        """
        filename = os.path.join(self.scan_dir, 'scan_report.csv')
        try:
            with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
                writer = csv.writer(f)
                
                # 写入扫描信息头
                writer.writerow(['扫描信息'])
                writer.writerow(['开始时间', datetime.fromtimestamp(scan_info['start_time']).strftime('%Y-%m-%d %H:%M:%S')])
                writer.writerow(['结束时间', datetime.fromtimestamp(scan_info['end_time']).strftime('%Y-%m-%d %H:%M:%S')])
                writer.writerow(['扫描主机数', scan_info['total_hosts']])
                writer.writerow(['扫描端口数', scan_info['total_ports']])
                writer.writerow(['开放端口数', len([r for r in results if r['status'] == 'open'])])
                writer.writerow([])  # 空行分隔
                
                # 写入表头
                headers = [
                    '目标IP',
                    '端口号',
                    '状态',
                    '服务',
                    '响应时间',
                    '产品名称',
                    '产品版本',
                    'Banner信息',
                    # 通用信息
                    '操作系统',
                    '认证方式',
                    # FTP特定信息
                    'FTP匿名访问',
                    'FTP支持特性',
                    # SSH特定信息
                    'SSH协议版本',
                    '密钥类型',
                    '密钥长度',
                    '密钥指纹',
                    'SSH加密算法',
                    # HTTP/HTTPS特定信息
                    'HTTP状态码',
                    '网页标题',
                    'Web服务器',
                    'HTTP头信息',
                    'SSL/TLS版本',
                    'SSL/TLS加密套件',
                    '证书主体',
                    '证书颁发者',
                    '证书过期时间',
                    # 数据库特定信息
                    '数据库类型',
                    '数据库版本',
                    '数据库认证方式',
                    '数据库SSL要求',
                    # 邮件服务特定信息
                    '邮件服务版本',
                    'STARTTLS支持',
                    'SSL/TLS支持',
                    # 远程桌面特定信息
                    '安全级别',
                    '加密级别',
                    '远程桌面系统信息'
                ]
                writer.writerow(headers)
                
                # 写入扫描结果
                status_map = {
                    'open': '开放',
                    'closed': '关闭',
                    'filtered': '被过滤'
                }
                
                for result in sorted(results, key=lambda x: (x['target'], x['port'])):
                    status = status_map.get(result['status'], result['status'])
                    service = result['service'] if result['service'] else "未知"
                    latency = f"{result['latency']}ms" if result['latency'] else "N/A"
                    
                    # 获取服务详细信息
                    info = result['service_info'] if result['service_info'] else {}
                    
                    # 基本信息
                    row_data = [
                        result['target'],
                        result['port'],
                        status,
                        service,
                        latency,
                        info.get('product', ''),
                        info.get('version', ''),
                        info.get('banner', ''),
                        info.get('os_type', ''),
                        ', '.join(info.get('auth_methods', [])),
                    ]
                    
                    # FTP特定信息
                    if info.get('name') == 'ftp':
                        row_data.extend([
                            '允许' if info.get('anonymous') else '禁止',
                            ', '.join(info.get('features', [])),
                        ])
                    else:
                        row_data.extend(['', ''])
                    
                    # SSH特定信息
                    if info.get('name') == 'ssh':
                        row_data.extend([
                            info.get('protocol_version', ''),
                            info.get('key_type', ''),
                            f"{info.get('key_bits', '')}位" if info.get('key_bits') else '',
                            info.get('fingerprint', ''),
                            ', '.join(info.get('encryption_algorithms', [])),
                        ])
                    else:
                        row_data.extend(['', '', '', '', ''])
                    
                    # HTTP/HTTPS特定信息
                    if info.get('name') in ['http', 'https']:
                        ssl_info = info.get('ssl_info', {})
                        http_headers = info.get('http_headers', {})
                        row_data.extend([
                            info.get('http_code', ''),
                            info.get('http_title', ''),
                            info.get('http_server', ''),
                            '; '.join([f"{k}: {v}" for k, v in http_headers.items()]),
                            ssl_info.get('version', ''),
                            ssl_info.get('cipher', ''),
                            ssl_info.get('cert_subject', ''),
                            ssl_info.get('cert_issuer', ''),
                            ssl_info.get('cert_expire', ''),
                        ])
                    else:
                        row_data.extend(['', '', '', '', '', '', '', '', ''])
                    
                    # 数据库特定信息
                    if info.get('name') in ['mysql', 'postgresql', 'mssql', 'oracle', 'mongodb']:
                        row_data.extend([
                            info.get('db_type', ''),
                            info.get('db_version', ''),
                            info.get('auth_method', ''),
                            '是' if info.get('requires_ssl') else '否',
                        ])
                    else:
                        row_data.extend(['', '', '', ''])
                    
                    # 邮件服务特定信息
                    if info.get('name') in ['smtp', 'pop3', 'imap']:
                        row_data.extend([
                            info.get('mail_version', ''),
                            '是' if info.get('starttls_supported') else '否',
                            '是' if info.get('ssl_tls_supported') else '否',
                        ])
                    else:
                        row_data.extend(['', '', ''])
                    
                    # 远程桌面特定信息
                    if info.get('name') in ['rdp', 'vnc']:
                        row_data.extend([
                            info.get('security_level', ''),
                            info.get('encryption_level', ''),
                            info.get('os_info', ''),
                        ])
                    else:
                        row_data.extend(['', '', ''])
                    
                    writer.writerow(row_data)
            
            logger.info(f"CSV报告已生成: {filename}")
        except Exception as e:
            logger.error(f"生成CSV报告时发生错误: {str(e)}") 